#!/bin/bash
# 修复文件句柄泄漏(Too many open files)的智能运维脚本
FIX_CODE_ID="file-handle-leak"

# 配置文件路径
LIMITS_CONF="/etc/security/limits.conf"
SYSTEMD_CONF_DIR="/etc/systemd/system/"
PROC_SYS_DIR="/proc/sys/fs/"

# 备份文件函数
backup_file() {
    local file="$1"
    if [ -f "$file" ]; then
        local backup="${file}.bak.$(date +%Y%m%d%H%M%S)"
        cp -a "$file" "$backup"
        echo "已备份配置文件: $backup"
    fi
}

# 显示当前文件句柄限制和使用情况
show_current_status() {
    echo "==== 当前文件句柄状态 ===="
    
    # 系统级限制
    echo "系统级最大文件句柄数: $(cat ${PROC_SYS_DIR}file-max)"
    
    # 进程级默认限制
    echo "默认进程最大文件句柄限制:"
    ulimit -a | grep "open files"
    
    # 当前系统文件句柄使用情况
    echo -e "\n当前系统文件句柄使用情况:"
    local total_open=$(lsof | wc -l)
    local max_open=$(cat ${PROC_SYS_DIR}file-max)
    local usage_pct=$(( total_open * 100 / max_open ))
    echo "总打开文件句柄数: $total_open (${usage_pct}% of $max_open)"
    
    # 按进程统计打开文件数
    echo -e "\n打开文件数最多的前10个进程:"
    lsof -n | awk '{print $2}' | sort | uniq -c | sort -nr | head -n 10 | while read count pid; do
        if [ -n "$pid" ] && [ "$pid" != "PID" ]; then
            cmd=$(ps -p $pid -o comm=)
            echo "$count  $pid  $cmd"
        fi
    done
    echo "=========================="
    echo
}

# 临时调整系统级文件句柄限制
temporary_system_tune() {
    echo "应用临时系统级文件句柄限制调整..."
    
    # 临时增加系统最大文件句柄数
    local current_max=$(cat ${PROC_SYS_DIR}file-max)
    local new_max=$(( current_max * 2 ))
    # 确保新值不小于100万
    if [ $new_max -lt 1000000 ]; then
        new_max=1000000
    fi
    
    echo $new_max > ${PROC_SYS_DIR}file-max
    echo "系统级最大文件句柄数已临时调整为: $new_max (之前为: $current_max)"
    
    # 临时调整其他相关参数
    echo 100000 > ${PROC_SYS_DIR}nr_open
    echo "已调整nr_open为: 100000"
    echo
}

# 永久调整系统级文件句柄限制
permanent_system_tune() {
    echo "应用永久系统级文件句柄限制调整..."
    
    # 备份sysctl配置
    if [ -f "/etc/sysctl.conf" ]; then
        backup_file "/etc/sysctl.conf"
        # 添加或修改fs.file-max配置
        if grep -q "^fs.file-max" /etc/sysctl.conf; then
            sed -i 's/^fs.file-max.*/fs.file-max = 1000000/' /etc/sysctl.conf
        else
            echo "fs.file-max = 1000000" >> /etc/sysctl.conf
        fi
        sysctl -p /etc/sysctl.conf &> /dev/null
    else
        # 对于使用sysctl.d的系统
        echo "fs.file-max = 1000000" > /etc/sysctl.d/99-file-handles.conf
        sysctl -p /etc/sysctl.d/99-file-handles.conf &> /dev/null
    fi
    
    echo "系统级最大文件句柄数已永久设置为1000000"
    echo
}

# 调整用户进程级文件句柄限制
tune_process_limits() {
    echo "调整用户进程级文件句柄限制..."
    
    backup_file "$LIMITS_CONF"
    
    # 添加或修改limits.conf配置
    local added=0
    if ! grep -q "^* soft nofile" "$LIMITS_CONF"; then
        echo "* soft nofile 65536" >> "$LIMITS_CONF"
        added=1
    else
        sed -i 's/^* soft nofile.*/\* soft nofile 65536/' "$LIMITS_CONF"
    fi
    
    if ! grep -q "^* hard nofile" "$LIMITS_CONF"; then
        echo "* hard nofile 100000" >> "$LIMITS_CONF"
        added=1
    else
        sed -i 's/^* hard nofile.*/\* hard nofile 100000/' "$LIMITS_CONF"
    fi
    
    # 为root用户单独设置
    if ! grep -q "^root soft nofile" "$LIMITS_CONF"; then
        echo "root soft nofile 65536" >> "$LIMITS_CONF"
        added=1
    else
        sed -i 's/^root soft nofile.*/root soft nofile 65536/' "$LIMITS_CONF"
    fi
    
    if ! grep -q "^root hard nofile" "$LIMITS_CONF"; then
        echo "root hard nofile 100000" >> "$LIMITS_CONF"
        added=1
    else
        sed -i 's/^root hard nofile.*/root hard nofile 100000/' "$LIMITS_CONF"
    fi
    
    if [ $added -eq 1 ]; then
        echo "已在$LIMITS_CONF中添加新的文件句柄限制"
    else
        echo "已更新$LIMITS_CONF中的文件句柄限制"
    fi
    echo
}

# 调整systemd服务的文件句柄限制
tune_systemd_services() {
    echo "调整systemd服务的文件句柄限制..."
    
    # 检查是否使用systemd
    if command -v systemctl &> /dev/null; then
        # 全局调整
        if [ ! -f "${SYSTEMD_CONF_DIR}system.conf.d/limits.conf" ]; then
            mkdir -p "${SYSTEMD_CONF_DIR}system.conf.d/"
            cat > "${SYSTEMD_CONF_DIR}system.conf.d/limits.conf" << EOF
[Manager]
DefaultLimitNOFILE=65536:100000
EOF
            echo "已创建systemd全局文件句柄限制配置"
        else
            echo "systemd全局文件句柄限制配置已存在"
        fi
        
        # 重新加载systemd配置
        systemctl daemon-reload &> /dev/null
        echo "已重新加载systemd配置"
    else
        echo "系统不使用systemd，跳过相关配置"
    fi
    echo
}

# 识别并处理可能的泄漏进程
handle_leaking_processes() {
    echo "检查可能存在文件句柄泄漏的进程..."
    
    # 找出打开文件数超过阈值的进程
    local threshold=1000
    local leaking_processes=$(lsof -n | awk '{print $2}' | sort | uniq -c | sort -nr | awk -v threshold=$threshold '$1 > threshold {print $2}')
    
    if [ -n "$leaking_processes" ]; then
        echo "发现可能存在文件句柄泄漏的进程:"
        echo "PID  打开文件数  进程名  建议操作"
        echo "-----------------------------------------"
        for pid in $leaking_processes; do
            count=$(lsof -p $pid 2>/dev/null | wc -l)
            cmd=$(ps -p $pid -o comm= 2>/dev/null)
            echo "$pid  $count  $cmd  重启进程"
        done
        
        echo -e "\n是否要重启这些进程? (y/N) "
        read -r response
        if [ "$response" = "y" ] || [ "$response" = "Y" ]; then
            for pid in $leaking_processes; do
                cmd=$(ps -p $pid -o comm= 2>/dev/null)
                echo "尝试重启进程 $pid ($cmd)..."
                # 尝试优雅重启
                kill -HUP $pid 2>/dev/null
                # 如果优雅重启失败，强制终止
                if ps -p $pid >/dev/null; then
                    echo "优雅重启失败，尝试强制终止进程 $pid..."
                    kill -9 $pid 2>/dev/null
                fi
            done
        else
            echo "已跳过进程重启，请稍后手动处理"
        fi
    else
        echo "未发现明显的文件句柄泄漏进程"
    fi
    echo
}

# 主逻辑
echo "开始处理文件句柄泄漏(Too many open files)问题..."
echo

# 显示当前状态
show_current_status

# 应用系统级调整
temporary_system_tune
permanent_system_tune

# 应用进程级调整
tune_process_limits
tune_systemd_services

# 处理泄漏进程
handle_leaking_processes

echo "==== 修复完成后的状态 ===="
echo "系统级最大文件句柄数: $(cat ${PROC_SYS_DIR}file-max)"
echo "请注意：新的进程级限制需要重新登录或重启进程才能生效"
echo "建议："
echo "1. 重新登录系统以应用新的用户级限制"
echo "2. 重启所有服务以应用新的文件句柄限制"
echo "3. 监控系统，确认问题是否已解决"
echo "4. 如问题持续，检查应用程序是否存在文件句柄泄漏bug"

exit 0
